package tests;

import static org.junit.Assert.*;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.UUID;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import sourceFiles.ChildEntry;
import sourceFiles.Ride;
import sourceFiles.RideState;

import Exceptions.InvalidAgeParam;
import Exceptions.InvalidCapacity;
import Exceptions.InvalidChildID;
import Exceptions.InvalidHeightParam;
import Exceptions.InvalidRideId;
import Exceptions.TicketDoesntExist;
import Exceptions.CentralComputerExceptions.ChildAgeDoesntMatch;
import Exceptions.CentralComputerExceptions.ChildDoesntHaveTicket;
import Exceptions.CentralComputerExceptions.ChildHeightDoesntMatch;
import Exceptions.CentralComputerExceptions.ChildIDDoesntExist;
import Exceptions.CentralComputerExceptions.RideIDDoesntExist;
import RideExceptions.ChildAlreadyInRideException;
import RideExceptions.ChildNotInRideException;
import RideExceptions.RideActiveException;
import RideExceptions.RideInactiveException;
import Stubs.CentralComputerStub3;
/**
 * This test class is a unit test for the class Ride
 * 
 * @author LASO-Team
 * 
 */
public class RideTest {
	// static parameters used in the test a defined here

	static Random random;
	private static final int RANDOM_TEST_SIZE = 7;
	private static final int[] EDGE_OF_LEGAL_INT_VALUES = { 1, 2 };
	private static final int[] EDGE_OF_ILLEGAL_INT_VALUES = { -1, 0 };
	private static final int TEST_SIZE = RANDOM_TEST_SIZE
			+ EDGE_OF_LEGAL_INT_VALUES.length;
	private static final int MAX_RANDOM = 99;

	static private int[] legalChildID;
	static private String[] legalRideNames;
	static private int[] legalRideID;
	static private int[] legalMaxCapacity;
	static private int[] legalMinAge;
	static private int[] legalMinHeight;
	static private boolean[] isExtremeRide;
	static private int[] illegalChildID;
	static private int[] illegalMaxCapacity;
	static private int[] illegalMinAge;
	static private int[] illegalMinHeight;
	static private int[] nonExistingChildID;

	static private CentralComputerStub3 centralComputerStub;
	static private LinkedList<Ride> rides;
	static private LinkedList<ChildEntry> children;

	// end static parameters

	/**
	 * Initiating all the static arrays above with test values, as well as
	 * random parameters (number of tests, TEST_SIZE, is determined by the
	 * number of test values as well as the number of random values
	 * 
	 * @throws Exception
	 */
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		assertTrue(EDGE_OF_LEGAL_INT_VALUES.length == EDGE_OF_ILLEGAL_INT_VALUES.length);
		random = new Random();
		LinkedList<Integer> legalChildIDList = new LinkedList<Integer>();
		LinkedList<Integer> legalRideIDList = new LinkedList<Integer>();
		LinkedList<Integer> legalMaxCapacitiesList = new LinkedList<Integer>();
		LinkedList<Integer> legalMinAgesList = new LinkedList<Integer>();
		LinkedList<Integer> legalMinHeightsList = new LinkedList<Integer>();
		LinkedList<Integer> illegalChildIDsList = new LinkedList<Integer>();
		// LinkedList<Integer> illegalRideIDsList = new LinkedList<Integer>();
		LinkedList<Integer> illegalMaxCapacitiesList = new LinkedList<Integer>();
		LinkedList<Integer> illegalMinAgesList = new LinkedList<Integer>();
		LinkedList<Integer> illegalMinHeightsList = new LinkedList<Integer>();

		for (int i = 0; i < EDGE_OF_LEGAL_INT_VALUES.length; i++) {
			legalChildIDList.add(EDGE_OF_LEGAL_INT_VALUES[i]);
			legalRideIDList.add(EDGE_OF_LEGAL_INT_VALUES[i]);
			legalMaxCapacitiesList.add(EDGE_OF_LEGAL_INT_VALUES[i]);
			legalMinAgesList.add(EDGE_OF_LEGAL_INT_VALUES[i]);
			legalMinHeightsList.add(EDGE_OF_LEGAL_INT_VALUES[i]);
			illegalChildIDsList.add(EDGE_OF_ILLEGAL_INT_VALUES[i]);
			// illegalRideIDsList.add(EDGE_OF_ILLEGAL_INT_VALUES[i]);
			illegalMaxCapacitiesList.add(EDGE_OF_ILLEGAL_INT_VALUES[i]);
			illegalMinAgesList.add(EDGE_OF_ILLEGAL_INT_VALUES[i]);
			illegalMinHeightsList.add(EDGE_OF_ILLEGAL_INT_VALUES[i]);
		}
		for (int i = 0; i < RANDOM_TEST_SIZE; i++) {
			int newRandomID = random.nextInt(MAX_RANDOM) + 1;
			while (legalChildIDList.contains(newRandomID)) {
				newRandomID = random.nextInt(MAX_RANDOM) + 1;
			}
			legalChildIDList.add(newRandomID);
			legalRideIDList.add(newRandomID);
			legalMaxCapacitiesList.add(random.nextInt(MAX_RANDOM) + 1);
			legalMinAgesList.add(random.nextInt(MAX_RANDOM) + 1);
			legalMinHeightsList.add(random.nextInt(MAX_RANDOM) + 1);
			illegalChildIDsList.add(-random.nextInt(MAX_RANDOM));
			// illegalRideIDsList.add(-random.nextInt(MAX_RANDOM));
			illegalMaxCapacitiesList.add(-random.nextInt(MAX_RANDOM));
			illegalMinAgesList.add(-random.nextInt(MAX_RANDOM));
			illegalMinHeightsList.add(-random.nextInt(MAX_RANDOM));
		}
		legalChildID = new int[TEST_SIZE];
		legalRideID = new int[TEST_SIZE];
		legalRideNames = new String[TEST_SIZE];
		legalMaxCapacity = new int[TEST_SIZE];
		legalMinAge = new int[TEST_SIZE];
		legalMinHeight = new int[TEST_SIZE];
		isExtremeRide = new boolean[TEST_SIZE];

		illegalChildID = new int[TEST_SIZE];
		illegalMaxCapacity = new int[TEST_SIZE];
		illegalMinAge = new int[TEST_SIZE];
		illegalMinHeight = new int[TEST_SIZE];
		nonExistingChildID = new int[TEST_SIZE];

		for (int i = 0; i < TEST_SIZE; i++) {

			legalChildID[i] = legalRideIDList.get(i);
			legalRideID[i] = legalRideIDList.get(i);
			legalRideNames[i] = UUID.randomUUID().toString();
			legalMaxCapacity[i] = legalMaxCapacitiesList.get(i);
			legalMinAge[i] = legalMinAgesList.get(i);
			legalMinHeight[i] = legalMinHeightsList.get(i);
			isExtremeRide[i] = random.nextBoolean();

			illegalChildID[i] = illegalChildIDsList.get(i);
			// illegalRideID[i] = illegalRideIDsList.get(i);
			illegalMaxCapacity[i] = illegalMaxCapacitiesList.get(i);
			illegalMinAge[i] = illegalMinAgesList.get(i);
			illegalMinHeight[i] = illegalMinHeightsList.get(i);

			int newRandomID = random.nextInt(MAX_RANDOM) + 1;
			while (legalChildIDList.contains(newRandomID)) {
				newRandomID = random.nextInt(MAX_RANDOM) + 1;
			}
			nonExistingChildID[i] = newRandomID;
			// nonExistingRideID[i] = newRandomID;
		}
	}

	/**
	 * Setting up a test with an appropriate stub for the CentralComputerStub3
	 * (simulating the different ride that will be built later, as well as the
	 * children that the system knows). finally, creating a list of rides on
	 * which to test the functionality
	 */
	@Before
	public void setUp() {
		centralComputerStub = new CentralComputerStub3();

		rides = new LinkedList<Ride>();
		for (int i = 0; i < TEST_SIZE; i++) {
			centralComputerStub.getRideIDList().add(legalRideID[i]);
			centralComputerStub.getMaxCapacityList().add(legalMaxCapacity[i]);
			centralComputerStub.getIsExtremeRideList().add(isExtremeRide[i]);
			centralComputerStub.getMinAgeList().add(legalMinAge[i]);
			centralComputerStub.getMinHeightList().add(legalMinHeight[i]);
			centralComputerStub.getChildIDList().add(legalChildID[i]);
			rides.add(new Ride(legalRideID[i], legalMaxCapacity[i],
					legalMinAge[i], legalMinHeight[i], legalRideNames[i],
					centralComputerStub));
		}
		children = new LinkedList<ChildEntry>();
	}

	/**
	 * This method tests that we cannot alter a ride to have a capacity less
	 * than 1.
	 * 
	 * @throws Exception
	 */
	@Test
	public void testConfigRideParams1() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				rides.get(i).configRideParams(illegalMaxCapacity[i],
						legalMinAge[i], legalMinHeight[i]);
				fail("tried to update a ride with an illegalMaxCapacity[" + i
						+ " = " + illegalMaxCapacity[i]);
			} catch (InvalidCapacity e) {
			}
		}
	}

	/**
	 * This method checks the we cannot alter a ride with minimal age less than
	 * 1.
	 * 
	 * @throws Exception
	 */
	@Test
	public void testConfigRideParams2() throws Exception {

		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				rides.get(i).configRideParams(legalMaxCapacity[i],
						illegalMinAge[i], legalMinHeight[i]);
				fail("tried to update a ride with an illegalMinAge[" + i
						+ " = " + illegalMinAge[i]);
			} catch (InvalidAgeParam e) {
			}
		}
	}

	/**
	 * This method tests that we cannot alter a ride with height less than 1.
	 * 
	 * @throws Exception
	 */
	@Test
	public void testConfigRideParams3() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				rides.get(i).configRideParams(legalMaxCapacity[i],
						legalMinAge[i], illegalMinHeight[i]);
				fail("tried to update a ride with an illegalMinHeight[" + i
						+ " = " + illegalMinHeight[i]);
			} catch (InvalidHeightParam e) {
			}
		}
	}

	/**
	 * This method tests that child cannot enter a ride with it's entrance gate
	 * closed
	 * 
	 * @throws Exception
	 */
	@Test
	public void testChildEntersRide1() throws Exception {
		Ride ride;
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				ride = rides.get(i);
				ride.getEntranceGate().closeGate();
				ride.childEntersRide(legalChildID[i]);
				fail("tried enter a child when Entrance gate is closed");
			} catch (RideActiveException e) {
			}
		}
	}

	/**
	 * This method tests that child ID cannot be smaller than 1.
	 * 
	 * @throws Exception
	 */
	@Test
	public void testChildEntersRide2() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				rides.get(i).childEntersRide(illegalChildID[i]);
				fail("tried enter a child with an  illegalChildID[" + i + " = "
						+ illegalChildID[i]);
			} catch (InvalidChildID e) {
			}
		}
	}

	/**
	 * This method tests that a child that isn't registered to the system cannot
	 * enter the ride.
	 */
	@Test
	public void testChildEntersRide3() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {

			try {
				rides.get(i).childEntersRide(nonExistingChildID[i]);
				fail("tried enter a child with a nonExistingChildID[" + i
						+ " = " + nonExistingChildID[i]);
			} catch (ChildIDDoesntExist e) {
			}
		}
	}

	/**
	 * This method tests that a child with age that isn't up to the
	 * specifications of the ride cannot enter the it.
	 */
	@Test
	public void testChildEntersRide4() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be to young to enter the ride
			centralComputerStub.getActualChildAge().add(legalMinAge[i] - 1);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(true);

		}
		Ride ride;
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				if (legalMinAge[i] == 1) {
					continue; // skip rides that will be closed for the next
								// child to enter (not relevant for this test)
				}
				ride = rides.get(i);
				ride.childEntersRide(legalChildID[i]);
				fail("tried enter a child with an  legalMinAge[" + i + " = "
						+ (legalMinAge[i] - 1)
						+ " that is smaller than the minAge " + legalMinAge[i]
						+ " allowed in the ride");
			} catch (ChildAgeDoesntMatch e) {
			}
		}
	}

	/**
	 * This method tests that a child with height that isn't up to the
	 * specifications of the ride cannot enter the it.
	 */
	@Test
	public void testChildEntersRide5() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be to short to enter the ride
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList()
					.add(legalMinHeight[i] - 1);
			centralComputerStub.setHasATicket(true);
		}
		Ride ride;
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				if (legalMinHeight[i] == 1) {
					continue; // skip rides that will be closed for the next
								// child to enter (not relevant for this test)
				}
				ride = rides.get(i);
				ride.childEntersRide(legalChildID[i]);
				fail("tried enter a child with an  legalMinHeight[" + i + " = "
						+ (legalMinHeight[i] - 1)
						+ " that is smaller than the minAge "
						+ legalMinHeight[i] + " allowed in the ride");
			} catch (ChildHeightDoesntMatch e) {
			}
		}
	}

	/**
	 * This method tests that if a child doesn't have a ticket to the ride he
	 * cannot enter the ride.
	 */
	@Test
	public void testChildEntersRide6() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will not have a ticket
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(false);
		}
		Ride ride;
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				ride = rides.get(i);
				ride.childEntersRide(legalChildID[i]);
				fail("tried enter a child without a ticket");
			} catch (TicketDoesntExist e) {
			}
		}
	}

	/**
	 * This method tests that a child matching all the requierments can enter
	 * the ride
	 * 
	 * @throws Exception
	 */
	@Test
	public void testChildEntersRide7() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be approved to enter the ride in the
			// central computer (only to pass validateChildForRide test)
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(true);

		}
		Ride ride;
		for (int i = 0; i < TEST_SIZE; i++) {
			ride = rides.get(i);
			ride.childEntersRide(legalChildID[i]);
		}
	}

	/**
	 * This method tests that cannot enter a ride if he's already inside
	 * 
	 * @throws Exception
	 */
	@Test
	public void testChildEntersRide8() throws Exception {
		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be approved to enter the ride in the
			// central computer (only to pass validateChildForRide test)
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(true);

		}
		Ride ride;
		for (int i = 0; i < TEST_SIZE; i++) {
			ride = rides.get(i);
			ride.childEntersRide(legalChildID[i]);
		}
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				if (legalMaxCapacity[i] == 1) {
					continue; // skip rides that will be closed for the next
								// child to enter (not relevant for this test)
				}
				ride = rides.get(i);
				ride.childEntersRide(legalChildID[i]);
				fail("tried enter a child with an  legalChildID[" + i + " = "
						+ legalChildID[i] + " that is already in the ride");
			} catch (ChildAlreadyInRideException e) {
			}
		}
	}

	/**
	 * This method tests that a child can exit the ride successfully
	 * 
	 * @throws Exception
	 */
	@Test
	public void testChildExitsRide1() throws Exception {

		rides = new LinkedList<Ride>();
		Ride ride;

		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be approved to enter the ride in the
			// central computer (only to pass validateChildForRide test)
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(true);
			ride = new Ride(legalRideID[i], legalMaxCapacity[i],
					legalMinAge[i], legalMinHeight[i], legalRideNames[i],
					centralComputerStub, 1); // ride will receive children for
												// about 1 second
			ride.childEntersRide(legalChildID[i]);
			rides.add(ride);
		}
		boolean areGatesEntranceOpened = true;
		do {
			Thread.sleep(1000);
			for (int i = 0; i < TEST_SIZE; i++) {
				ride = rides.get(i);
				if (i > 0) {
					areGatesEntranceOpened = ride.getEntranceGate()
							.IsGateOpened() | areGatesEntranceOpened;
				} else {
					areGatesEntranceOpened = ride.getEntranceGate()
							.IsGateOpened();
				}
			}
		} while (areGatesEntranceOpened);
		for (int i = 0; i < TEST_SIZE; i++) {
			ride = rides.get(i);
			ride.childExitsRide(legalChildID[i]);
		}
	}

	/**
	 * This method a child that entered the ride cannot exit the ride while the
	 * ride can admit more children (they are locked in! Muhahahah!!)
	 * 
	 * @throws Exception
	 */
	@Test
	public void testChildExitsRide2() throws Exception {

		rides = new LinkedList<Ride>();
		Ride ride;

		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be approved to enter the ride in the
			// central computer (only to pass validateChildForRide test)
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(true);
			ride = new Ride(legalRideID[i], legalMaxCapacity[i],
					legalMinAge[i], legalMinHeight[i], legalRideNames[i],
					centralComputerStub, 1); // ride will receive children for
												// about 1 second
			ride.childEntersRide(legalChildID[i]);
			rides.add(ride);
		}

		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				ride = rides.get(i);
				if (legalMaxCapacity[i] == 1) {
					continue; // skip ride which will be closed by entering 1
								// child to them
				}
				ride.childExitsRide(legalChildID[i]);
				fail("tried to leave a ride with a child with a  legalChildID["
						+ i + "] = " + legalChildID[i]
						+ " while ride can admit more children");
			} catch (RideInactiveException e) {
			}
		}
	}

	/**
	 * This method a child that a child that leaves a ride must have entered the
	 * ride before
	 * 
	 * @throws Exception
	 */
	@Test
	public void testChildExitsRide3() throws Exception {

		rides = new LinkedList<Ride>();
		Ride ride;

		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be approved to enter the ride in the
			// central computer (only to pass validateChildForRide test)
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(true);
			ride = new Ride(legalRideID[i], legalMaxCapacity[i],
					legalMinAge[i], legalMinHeight[i], legalRideNames[i],
					centralComputerStub, 1); // ride will receive children for
												// about 1 second
			ride.childEntersRide(legalChildID[i]);
			rides.add(ride);
		}
		boolean areGatesEntranceOpened = true;
		do {
			Thread.sleep(1000);
			for (int i = 0; i < TEST_SIZE; i++) {
				ride = rides.get(i);
				if (i > 0) {
					areGatesEntranceOpened = ride.getEntranceGate()
							.IsGateOpened() | areGatesEntranceOpened;
				} else {
					areGatesEntranceOpened = ride.getEntranceGate()
							.IsGateOpened();
				}
			}
		} while (areGatesEntranceOpened);
		for (int i = 0; i < TEST_SIZE; i++) {
			try {
				ride = rides.get(i);
				ride.childExitsRide(nonExistingChildID[i]);
				fail("tried to leave a ride with a child with a nonExistingChildID["
						+ i
						+ "] = "
						+ nonExistingChildID[i]
						+ " that was not in the ride");
			} catch (ChildNotInRideException e) {
			}
		}
	}

	/**
	 * This method checks that the ride cycle behaves as expected in the design
	 * of the park.
	 * 
	 * @throws Exception
	 */
	@Test
	public void testRideCycle() throws Exception {

		for (int i = 0; i < TEST_SIZE; i++) {
			// adding children that will be approved to enter the ride in the
			// central computer (only to pass validateChildForRide test)
			centralComputerStub.getActualChildAge().add(legalMinAge[i]);
			centralComputerStub.getActualHeightList().add(legalMinHeight[i]);
			centralComputerStub.setHasATicket(true);
		}

		Ride ride = new Ride(legalRideID[2], legalMaxCapacity[2],
				legalMinAge[2], legalMinHeight[2], legalRideNames[2],
				centralComputerStub, 1); // ride will receive children for
											// about 1 second
		for (int i = 0; i < 1000; i++) {
			Thread.sleep(10);
			assertTrue(ride.getEntranceGate().IsGateOpened());
			assertTrue(!ride.getExitGate().IsGateOpened());
			assertTrue(ride.getState() == RideState.STANDBY);
		}
		ride = new Ride(legalRideID[2], legalMaxCapacity[2], legalMinAge[2],
				legalMinHeight[2], legalRideNames[2], centralComputerStub, 1); // ride
																				// will
																				// receive
																				// children
																				// for
																				// about
																				// 1
																				// second
		ride.childEntersRide(legalChildID[2]);
		while (ride.getEntranceGate().IsGateOpened()) {
			Thread.sleep(1000);
		}
		for (int i = 0; i < 1000; i++) {
			assertTrue(!ride.getEntranceGate().IsGateOpened());
			assertTrue(ride.getExitGate().IsGateOpened());
			assertTrue(ride.getState() == RideState.RUNNING);
			Thread.sleep(10);
		}
		ride.childExitsRide(legalChildID[2]);
		for (int i = 0; i < 1000; i++) {
			Thread.sleep(10);
			assertTrue(ride.getEntranceGate().IsGateOpened());
			assertTrue(!ride.getExitGate().IsGateOpened());
			assertTrue(ride.getState() == RideState.STANDBY);
		}
	}

}
